package com.jzo2o.orders.manager.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.db.DbRuntimeException;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.orders.dto.response.OrderResDTO;
import com.jzo2o.api.orders.dto.response.OrderSimpleResDTO;
import com.jzo2o.common.constants.UserType;
import com.jzo2o.common.enums.EnableStatusEnum;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.common.utils.JsonUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.orders.base.config.OrderStateMachine;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderRefundStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.domain.OrdersCanceled;
import com.jzo2o.orders.base.model.domain.OrdersRefund;
import com.jzo2o.orders.base.model.dto.OrderSnapshotDTO;
import com.jzo2o.orders.base.model.dto.OrderUpdateStatusDTO;
import com.jzo2o.orders.base.service.IOrdersCommonService;
import com.jzo2o.orders.manager.handler.OrdersHandler;
import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
import com.jzo2o.orders.manager.service.IOrdersCanceledService;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import com.jzo2o.orders.manager.service.IOrdersManagerService;
import com.jzo2o.orders.manager.service.IOrdersRefundService;
import com.jzo2o.redis.helper.CacheHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.jzo2o.orders.base.constants.FieldConstants.SORT_BY;
import static com.jzo2o.orders.base.constants.RedisConstants.RedisKey.ORDERS;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersManagerServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersManagerService {

    //注入自己,这个就是一个代理对象
    @Resource
    private  OrdersManagerServiceImpl owner;
    @Resource
    private IOrdersCanceledService ordersCanceledService;
    @Resource
    private IOrdersCommonService ordersCommonService;
    @Resource
    private IOrdersCreateService ordersCreateService;
    @Resource
    private IOrdersRefundService ordersRefundService;
    @Resource
    private OrdersHandler ordersHandler;
    @Resource
    private OrderStateMachine orderStateMachine;
    @Resource
    private CacheHelper cacheHelper;   //老师自己开发的组件,用来把数据列表存到hash中,,以前我们学的springcache只支持字符串格式
        
    @Override
    public List<Orders> batchQuery(List<Long> ids) {
        LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery().in(Orders::getId, ids).ge(Orders::getUserId, 0);
        return baseMapper.selectList(queryWrapper);
    }

    @Override
    public Orders queryById(Long id) {
        return baseMapper.selectById(id);
    }

    /**
     * 滚动分页查询
     *
     * @param currentUserId 当前用户id
     * @param ordersStatus  订单状态，0：待支付，100：派单中，200：待服务，300：服务中，400：待评价，500：订单完成，600：已取消，700：已关闭
     * @param sortBy        排序字段
     * @return 订单列表
     */
    @Override
    public List<OrderSimpleResDTO> consumerQueryList(Long currentUserId, Integer ordersStatus, Long sortBy) {
        //1.构件查询条件
        LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery()
                .eq(ObjectUtils.isNotNull(ordersStatus), Orders::getOrdersStatus, ordersStatus)
                .eq(Orders::getUserId, currentUserId)
                .eq(Orders::getDisplay, EnableStatusEnum.ENABLE.getStatus())
                .lt(ObjectUtils.isNotNull(sortBy), Orders::getSortBy, sortBy)
                .select(Orders::getId);//指定查询的字段是订单id
        
        Page<Orders> queryPage = new Page<>();
        queryPage.addOrder(OrderItem.desc(SORT_BY));
        queryPage.setSearchCount(false);

        //2.使用覆盖索引查询订单列表(只包括订单id,上面sql语句查询的就是覆盖索引)
        Page<Orders> ordersPage = baseMapper.selectPage(queryPage, queryWrapper);
        //将查询到的列表提取出订单id
        List<Orders> orderList = ordersPage.getRecords();
        //提取订单id
        List<Long> ids = CollUtils.getFieldValues(orderList, Orders::getId);

        //根据订单id查询聚集索引,使用sql: select * from orders where id in(?,?,?,?)
        //List<Orders> ordersList = batchQuery(ids);
        
        //将订单信息存入缓存，如果缓存有从缓存查; 如果没有才从数据库查
        //方法中第一个参数:  String dataType(redis key)
        String redisKey = String.format(ORDERS, currentUserId);
        // 第二个参数: List<K> objectIds 订单id（多个）
        // BatchDataQueryExecutor<K, T> batchDataQueryExecutor  有一个 Map<K, T> execute(List<K> objectIds（缓存中没有的id）, Class<T> clazz 返回的数据类型);
        //Class<T> clazz 最终返回的数据类型
        // Long ttl 缓存过期时间
        
        //把上面的参数都传入到这个cacheHelper方法中,可以看到还有一个函数式参数,,
            // 这里不理解多看视频,day09_v6.用户订单列表优化-查询订单缓存
        List<OrderSimpleResDTO> orderSimpleResDTOS = cacheHelper.<Long, OrderSimpleResDTO>batchGet(redisKey, ids, 
                (noCacheIds, clazz) -> {
            //查询数据库
            List<Orders> ordersList = batchQuery(noCacheIds);   //noCacheIds是没有缓存的id,也就是说缓存中没有对应的数据,就要查询数据库了;然后存到redis缓存中
                //这里不理解就跟着老师视频,追源码debug一下就明白了 

            //将ordersList转成map
            Map<Long, OrderSimpleResDTO> collect = ordersList.stream().collect
                    (Collectors.toMap(Orders::getId, o -> BeanUtils.toBean(o, OrderSimpleResDTO.class)));
            return collect;

        }, OrderSimpleResDTO.class, 600L);
        //List<Orders> records = ordersPage.getRecords();
        //List<OrderSimpleResDTO> orderSimpleResDTOS = BeanUtil.copyToList(ordersList, OrderSimpleResDTO.class);
        
        return orderSimpleResDTOS;

    }
    ///**
    // * 滚动分页查询
    // *
    // * @param currentUserId 当前用户id
    // * @param ordersStatus  订单状态，0：待支付，100：派单中，200：待服务，300：服务中，400：待评价，500：订单完成，600：已取消，700：已关闭
    // * @param sortBy        排序字段
    // * @return 订单列表
    // */
    //@Override
    //public List<OrderSimpleResDTO> consumerQueryList(Long currentUserId, Integer ordersStatus, Long sortBy) {
    //    //1.构件查询条件
    //    LambdaQueryWrapper<Orders> queryWrapper = Wrappers.<Orders>lambdaQuery()
    //            .eq(ObjectUtils.isNotNull(ordersStatus), Orders::getOrdersStatus, ordersStatus)
    //            .eq(Orders::getUserId, currentUserId)
    //            .eq(Orders::getDisplay, EnableStatusEnum.ENABLE.getStatus())
    //            .lt(ObjectUtils.isNotNull(sortBy), Orders::getSortBy, sortBy);
    //
    //    Page<Orders> queryPage = new Page<>();
    //    queryPage.addOrder(OrderItem.desc(SORT_BY));
    //    queryPage.setSearchCount(false);
    //
    //    //2.查询订单列表
    //    Page<Orders> ordersPage = baseMapper.selectPage(queryPage, queryWrapper);
    //    List<Orders> records = ordersPage.getRecords();
    //    List<OrderSimpleResDTO> orderSimpleResDTOS = BeanUtil.copyToList(records, OrderSimpleResDTO.class);
    //    return orderSimpleResDTOS;
    //
    //}
    /**
     * 根据订单id查询
     *
     * @param id 订单id
     * @return 订单详情
     */
    @Override
    public OrderResDTO getDetail(Long id) {
        //Orders orders = queryById(id);
        //对上面注释的代码进行优化,,"第七章 系统优化讲义_3.2",通过状态机的快照查询代码,,而不直接去数据库查询了
        
        //查询订单快照(得到json数据)--需要注入状态机,快照是状态机的,前面章节讲过,忘了回顾状态机优化部分的讲义
        String currentSnapshotCache = orderStateMachine.getCurrentSnapshotCache(id.toString());
        //把上面的字符串再转回对象(最开始封装快照就是这个对象)
        OrderSnapshotDTO orderSnapshotDTO = JsonUtils.toBean(currentSnapshotCache, OrderSnapshotDTO.class);

        //补充代码: 懒加载方式来取消支付超时的订单----单独定义方法,然后调用,,不用在这里加一大堆逻辑
            //原来传的是orders,然后优化状态机快照查询的时候,,,传入的是快照对象orderSnapshotDTO了,,
            //老师说这个懒加载,也只在当前这里用到了,别的地方没有,就没有单独转换成orders对象,,
        orderSnapshotDTO = canalIfPayOvertime(orderSnapshotDTO);
        OrderResDTO orderResDTO = BeanUtil.toBean(orderSnapshotDTO, OrderResDTO.class);
        return orderResDTO;
    }
    /**
     * 懒加载方式取消支付超时的 订单
     * 优化版: 就是你看这个方法下面,注释掉的代码,,原来是传入的orders对象,现在改成传入快照对象了OrderSnapshotDTO
     * @param orderSnapshotDTO
     * @return
     */
    public OrderSnapshotDTO canalIfPayOvertime(OrderSnapshotDTO orderSnapshotDTO){
        //订单状态
        Integer ordersStatus = orderSnapshotDTO.getOrdersStatus();
        //判断订单是未支付且支付超时(从订单创建时间开始15分钟未支付)
        //下面的isBefore就是小于的意思;下面这两种方式都可以,,效果一样
        //if(ordersStatus==OrderStatusEnum.NO_PAY.getStatus() && orders.getCreateTime().isBefore(LocalDateTime.now().minusMinutes(15)) ){
        if(ordersStatus==OrderStatusEnum.NO_PAY.getStatus() && orderSnapshotDTO.getCreateTime().plusMinutes(15).isBefore(LocalDateTime.now())){
            //下面为啥还要再查询一下状态,,直接取消订单不就行了;;这样不严谨
            //如果,超过了15分钟,用户又支付了订单你怎么办,这里订单系统已经修改成了取消状态,,,
            // 所以可以先查一下状态,已支付就不走下面取消订单的逻辑,,直接跳出if,返回原来的订单信息就可以了

            //查询一下最新的支付状态，如果没有支付成功，再执行下边的取消代码
            OrdersPayResDTO payResultFromTradServer = ordersCreateService.getPayResultFromTradServer(orderSnapshotDTO.getId());
            //如果没有支付成功，再执行下边的取消代码
            if(ObjectUtils.isNotNull(payResultFromTradServer) && payResultFromTradServer.getPayStatus()!= OrderPayStatusEnum.PAY_SUCCESS.getStatus()){
                OrderCancelDTO orderCancelDTO = BeanUtils.toBean(orderSnapshotDTO,OrderCancelDTO.class);
                //需要注意,,这里是让系统去处理取消超时的订单,,所以用户类型还有取消原因,,就不能再写c端用户那个了
                orderCancelDTO.setCurrentUserType(UserType.SYSTEM);
                orderCancelDTO.setCancelReason("订单支付超时系统自动取消");
                cancelByNoPay(orderCancelDTO);

                //因为还要把取消的订单信息返回,返回Orders
                //查询最新的订单信息
                //orders = getById(orders.getId());
                //上面注释掉的代码: 以前是查询的数据库,现在就得查询快照了
                    //上面执行cancelByNoPay方法因为使用了状态机,取消订单后存入了新的快照,因此这里要查询新的快照
                String currentSnapshot = orderStateMachine.getCurrentSnapshot(orderSnapshotDTO.getId().toString());
                //把拿到的快照从string转回对象
                orderSnapshotDTO = JsonUtils.toBean(currentSnapshot, OrderSnapshotDTO.class);
                return orderSnapshotDTO;
            }
        }
        //如果上面的if不满足条件,,就还返回原来的快照订单信息(逻辑真清楚,自己想不到)
        return orderSnapshotDTO;
    }
    
    
    ///**
    // * 懒加载方式取消支付超时的 订单
    // * @param orders
    // * @return
    // */
    //public Orders canalIfPayOvertime(Orders orders){
    //    //订单状态
    //    Integer ordersStatus = orders.getOrdersStatus();
    //    //判断订单是未支付且支付超时(从订单创建时间开始15分钟未支付)
    //        //下面的isBefore就是小于的意思;下面这两种方式都可以,,效果一样
    //   //if(ordersStatus==OrderStatusEnum.NO_PAY.getStatus() && orders.getCreateTime().isBefore(LocalDateTime.now().minusMinutes(15)) ){
    //    if(ordersStatus==OrderStatusEnum.NO_PAY.getStatus() && orders.getCreateTime().plusMinutes(15).isBefore(LocalDateTime.now())){
    //        //下面为啥还要再查询一下状态,,直接取消订单不就行了;;这样不严谨
    //            //如果,超过了15分钟,用户又支付了订单你怎么办,这里订单系统已经修改成了取消状态,,,
    //            // 所以可以先查一下状态,已支付就不走下面取消订单的逻辑,,直接跳出if,返回原来的订单信息就可以了
    //        
    //        //查询一下最新的支付状态，如果没有支付成功，再执行下边的取消代码
    //        OrdersPayResDTO payResultFromTradServer = ordersCreateService.getPayResultFromTradServer(orders.getId());
    //        //如果没有支付成功，再执行下边的取消代码
    //        if(ObjectUtils.isNotNull(payResultFromTradServer) && payResultFromTradServer.getPayStatus()!= OrderPayStatusEnum.PAY_SUCCESS.getStatus()){
    //            OrderCancelDTO orderCancelDTO = BeanUtils.toBean(orders,OrderCancelDTO.class);
    //            //需要注意,,这里是让系统去处理取消超时的订单,,所以用户类型还有取消原因,,就不能再写c端用户那个了
    //            orderCancelDTO.setCurrentUserType(UserType.SYSTEM);
    //            orderCancelDTO.setCancelReason("订单支付超时系统自动取消");
    //            cancelByNoPay(orderCancelDTO);
    //
    //            //因为还要把取消的订单信息返回,返回Orders
    //            //查询最新的订单信息
    //            orders = getById(orders.getId());
    //            return orders;
    //        }
    //    }
    //    //如果上面的if不满足条件,,就还返回原来的orders订单信息(逻辑真清楚,自己想不到)
    //    return orders;
    //}
    

    /**
     * 订单评价
     *
     * @param ordersId 订单id
     */
    @Override
    @Transactional
    public void evaluationOrder(Long ordersId) {
//        //查询订单详情
//        Orders orders = queryById(ordersId);
//
//        //构建订单快照
//        OrderSnapshotDTO orderSnapshotDTO = OrderSnapshotDTO.builder()
//                .evaluationTime(LocalDateTime.now())
//                .build();
//
//        //订单状态变更
//        orderStateMachine.changeStatus(orders.getUserId(), orders.getId().toString(), OrderStatusChangeEventEnum.EVALUATE, orderSnapshotDTO);
    }

    
    /**
     * 取消订单
     * @param orderCancelDTO 取消订单模型
     * 在第七章系统优化讲义_1.2.4取消订单使用状态机 （实战）;由于我没找到源码,所以下面我就没改代码;
     *     取消订单的代码要优化成自定义的事件和动作,,我都没改,主要是没源码,我对照都没法对照;
     *     要改,就参考讲义"1.2.3 使用订单状态机",老师视频讲了,在下单方法中,启动状态机;在支付成功方法中,通过状态机修改状态,可以参考
     */
    @Override
    public void cancel(OrderCancelDTO orderCancelDTO) {
        //订单id
        Long orderId = orderCancelDTO.getId();

        //判断该订单是否存在
        Orders orders = getById(orderId);   //mybatisplus提供的方法,,所以这里直接用的
        if (ObjectUtils.isNull(orders)) {
            throw new CommonException("要取消的订单不存在");
        }
        //将订单中的交易单号信息拷贝到orderCancelDTO
        orderCancelDTO.setTradingOrderNo(orders.getTradingOrderNo());
        orderCancelDTO.setRealPayAmount(orders.getRealPayAmount());
        //取出订单状态
        Integer ordersStatus = orders.getOrdersStatus();

        //对未支付订单的取消操作
        if(ordersStatus== OrderStatusEnum.NO_PAY.getStatus()){
            //老师说这里就不要再if里面写一大堆的代码逻辑了,,所以单独提出来一个方法来实现
            //下面的cancelByNoPay是一个事务方法,,所以这里要注入自己的代理对象调用
            owner.cancelByNoPay(orderCancelDTO);
        }else if(ordersStatus== OrderStatusEnum.DISPATCHING.getStatus()){ //对已支付(派单中)订单的操作
            //向数据库保存三条记录
                //添加取消订单记录; 更新订单状态为"已关闭";  添加退款记录
            owner.cancelByDispatching(orderCancelDTO);
            //新启动一个线程请求支付服务进行退款
            ordersHandler.requestRefundNewThread(orderCancelDTO.getId());
        }else{
            throw new CommonException("当前订单状态不支持取消操作");
        }
    }

    /**
     * 取消未支付的订单
     * 单独定义的一个方法,,然后上面上面cancel方法里面可以直接调用这个方法
     * @param orderCancelDTO
     */
    @Transactional(rollbackFor = Exception.class)  //事务方法,,事务方法下面修饰符得用public,用private还会报错
    public void cancelByNoPay(OrderCancelDTO orderCancelDTO) {

        //添加订单取消记录
        OrdersCanceled ordersCanceled = BeanUtils.toBean(orderCancelDTO,OrdersCanceled.class);
        //取消人id
        ordersCanceled.setCancellerId(orderCancelDTO.getCurrentUserId());
        //取消人名称
        ordersCanceled.setCancelerName(orderCancelDTO.getCurrentUserName());
        //取消人的类型
        ordersCanceled.setCancellerType(orderCancelDTO.getCurrentUserType());
        //取消时间,,,这里就设置成了当前操作的时间
        ordersCanceled.setCancelTime(LocalDateTime.now());
        ordersCanceledService.save(ordersCanceled);

        //修改订单状态为已取消
        OrderUpdateStatusDTO orderUpdateStatusReqDTO = new OrderUpdateStatusDTO();
        //填充订单id
        orderUpdateStatusReqDTO.setId(orderCancelDTO.getId());
        //将未支付的状态最终改为已取消
        //原始状态-未支付
            //这是最开始的状态,,我们的目的,是将未支付的状态,,修改为已取消的状态
        orderUpdateStatusReqDTO.setOriginStatus(OrderStatusEnum.NO_PAY.getStatus());
        //目标状态-已取消
        orderUpdateStatusReqDTO.setTargetStatus(OrderStatusEnum.CANCELED.getStatus());
        //最终的执行sql: update orders set orders_status=600 where id=? and orders_status=0
            //这个600就是已取消的状态;  后面的orders_status=0表示的是原始状态,,0表示是未支付的状态
            //这个修改状态,很多地方都要用到,,所以也抽取了一个方法updateStatus单独定义
        Integer integer = ordersCommonService.updateStatus(orderUpdateStatusReqDTO);
        if(integer<=0){  //如果未执行成功
            throw new DbRuntimeException("订单取消事件处理失败");
        }
    }

    /**
     * 取消派单中的订单
     * 上面cancel方法会调用这个
     */
    @Transactional(rollbackFor = Exception.class)
    public void cancelByDispatching(OrderCancelDTO orderCancelDTO){
        //添加订单取消记录
        OrdersCanceled ordersCanceled = BeanUtils.toBean(orderCancelDTO,OrdersCanceled.class);
        //取消人id
        ordersCanceled.setCancellerId(orderCancelDTO.getCurrentUserId());
        //取消人名称
        ordersCanceled.setCancelerName(orderCancelDTO.getCurrentUserName());
        //取消人的类型
        ordersCanceled.setCancellerType(orderCancelDTO.getCurrentUserType());
        //取消时间
        ordersCanceled.setCancelTime(LocalDateTime.now());
        ordersCanceledService.save(ordersCanceled);

        //修改订单状态为已关闭
        OrderUpdateStatusDTO orderUpdateStatusReqDTO = new OrderUpdateStatusDTO();
        //填充订单id
        orderUpdateStatusReqDTO.setId(orderCancelDTO.getId());
        //将未支付的状态最终改为已取消
        //原始状态-派单中
        orderUpdateStatusReqDTO.setOriginStatus(OrderStatusEnum.DISPATCHING.getStatus());
        //目标状态-已关闭
        orderUpdateStatusReqDTO.setTargetStatus(OrderStatusEnum.CLOSED.getStatus());
        //设置退款状态为1退款中
        orderUpdateStatusReqDTO.setRefundStatus(OrderRefundStatusEnum.REFUNDING.getStatus());
        //最终的执行sql: update orders set orders_status=700 where id=? and orders_status=100
        Integer integer = ordersCommonService.updateStatus(orderUpdateStatusReqDTO);
        if(integer<=0){
            throw new DbRuntimeException("订单取消事件处理失败");
        }

        //保存退款记录(感觉是要退款的记录)
        OrdersRefund ordersRefund = BeanUtils.toBean(orderCancelDTO,OrdersRefund.class);
        ordersRefundService.save(ordersRefund);


    }
    
    

}
